﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace QUT.Bio.Util {
	public partial class TwirlyThing : UserControl {
		//UI elements
		private List<Line> lines = new List<Line>();
		private List<Ellipse> circles1 = new List<Ellipse>();
		private List<Ellipse> circles2 = new List<Ellipse>();

		//Properties of the DNA sine wave
		private double amplitude;
		private double frequency;
		private int linesPerPeriod;
		private double phase;
		private double phaseChange;
		private int timeInterval;

		private double lineThickness = 2;
		private double blobSize = 4;
		private Brush blobFill = new SolidColorBrush( Color.FromArgb( 255, 50, 200, 200 ) );
		private Color lineStartColor = Color.FromArgb( 255, 50, 200, 200 );
		private Color lineEndColor = Color.FromArgb( 100, 50, 200, 200 );

		private System.Windows.Threading.DispatcherTimer myDispatcherTimer;

		private bool isStarted = false;

		public TwirlyThing() {
			InitializeComponent();

			//change these for different effects
			frequency = 1.5;
			linesPerPeriod = 20;
			phase = 0;
			phaseChange = 5;
			timeInterval = 75;

			myDispatcherTimer = new System.Windows.Threading.DispatcherTimer();
			myDispatcherTimer.Interval = new TimeSpan( 0, 0, 0, 0, timeInterval );
			myDispatcherTimer.Tick += new EventHandler( myDispatcherTimer_Tick );

			Visibility = Visibility.Collapsed;

			CreateStroke();
			
			this.SizeChanged += new SizeChangedEventHandler( LoadingPanel_SizeChanged );
		}

		public double LineThickness {
			get {
				return lineThickness;
			}
			set {
				lineThickness = value;

				foreach ( Line line in lines ) {
					line.StrokeThickness = value;
				}
			}
		}

		private void DoBlobs( Action<Ellipse> action ) {
			foreach ( Ellipse ellipse in circles1 ) {
				action( ellipse );
			}
			
			foreach ( Ellipse ellipse in circles2 ) {
				action( ellipse );
			}
		}
		 
		public double BlobSize {
			get {
				return blobSize;
			}
			set {
				blobSize = value;

				DoBlobs( delegate ( Ellipse e ) {
					double x = (double) e.GetValue( Canvas.LeftProperty ) + e.Width / 2;
					double y = (double) e.GetValue( Canvas.TopProperty ) + e.Width / 2;
					e.Width = value;
					e.Height = value;
					e.SetValue( Canvas.LeftProperty, x - value / 2 );
					e.SetValue( Canvas.TopProperty, y - value / 2 );
				} );
			}
		}
		
		public Brush BlobFill {
			get {
				return blobFill;
			}
			set {
				blobFill = value;

				DoBlobs( delegate ( Ellipse e ) {
					e.Fill = blobFill;
				} );
			}
		}

		public Color LineStartColor {
			get {
				return lineStartColor;
			}
			set {
				lineStartColor = value;
				lineStartGradientStop.Color = lineStartColor;
			}
		}

		public Color LineEndColor {
			get {
				return lineEndColor;
			}
			set {
				lineEndColor = value;
				lineEndGradientStop.Color = lineEndColor;
			}
		}

		//Redraw the lines when the panel is resized
		void LoadingPanel_SizeChanged( object sender, SizeChangedEventArgs e ) {
			//System.Diagnostics.Debug.WriteLine( String.Format(
			//    "Size changed from {0}x{1} to {2}x{3}",
			//    e.PreviousSize.Width,
			//    e.PreviousSize.Height,
			//    e.NewSize.Width,
			//    e.NewSize.Height
			//) );
			
			bool started = isStarted;
			Stop();

			if ( started ) {
				Start();
			}
		}


		/// <summary>
		/// Show and begin the animation
		/// </summary>
		public void Start() {
			isStarted = true;
			Visibility = Visibility.Visible;
			createLines();
			myDispatcherTimer.Start();
		}

		/// <summary>
		/// Stop and hide the animation
		/// </summary>
		public void Stop() {
			isStarted = false;
			Visibility = Visibility.Collapsed;
			myDispatcherTimer.Stop();
		}
		
		
		LinearGradientBrush stroke = new LinearGradientBrush();
		GradientStop lineStartGradientStop = new GradientStop();
		GradientStop lineEndGradientStop = new GradientStop();

		private void CreateStroke () {
			stroke.StartPoint = new Point( 0, 0 );
			stroke.EndPoint = new Point( 0, 1 );
			stroke.GradientStops = new GradientStopCollection();

			lineStartGradientStop.Color = lineStartColor;
			lineStartGradientStop.Offset = 0;

			lineEndGradientStop.Color = lineEndColor;
			lineEndGradientStop.Offset = 1;

			stroke.GradientStops.Add( lineStartGradientStop );
			stroke.GradientStops.Add( lineEndGradientStop );
		}
		
		/// <summary>
		/// Draw the lines in their initial positions (X positions)
		/// </summary>
		private void createLines() {
			canvas.Children.Clear();
			lines.Clear();
			circles1.Clear();
			circles2.Clear();

			if ( ActualWidth == 0 || ActualHeight == 0 ) {
				//System.Diagnostics.Debug.WriteLine( "Width or heigh = 0: returning." );
				return;
			}

			amplitude = ActualHeight / 2 - blobSize;
			
			double period = 360 / frequency;
			double spacing = period / linesPerPeriod;
			int n = (int) Math.Floor(ActualWidth / spacing);
			double offset = ( ActualWidth - n * spacing ) / 2;

			for ( int i = 0; i <= n; i++ ) {
				double x = offset + i * spacing;
				
				Line l = new Line();
				l.Stroke = stroke;

				l.StrokeThickness = lineThickness;
				l.X1 = x;
				l.X2 = x;
				l.Y1 = ActualHeight / 2;
				l.Y2 = ActualHeight / 2;

				lines.Add( l );

				Ellipse el1 = new Ellipse();
				el1.SetValue( Canvas.LeftProperty, x - blobSize / 2 );
				el1.Width = blobSize;
				el1.Height = blobSize;
				el1.Stroke = blobFill;
				el1.Fill = blobFill;
				el1.Fill.Opacity = 0.85;
				el1.SetValue( Canvas.ZIndexProperty, 5 );
				circles1.Add( el1 );

				Ellipse el2 = new Ellipse();
				el2.SetValue( Canvas.LeftProperty, x - blobSize / 2 );
				el2.Width = blobSize;
				el2.Height = blobSize;
				el2.Stroke = blobFill;
				el2.Fill = blobFill;
				el2.Fill.Opacity = 0.85;
				el2.SetValue( Canvas.ZIndexProperty, 5 );
				circles2.Add( el2 );

				canvas.Children.Add( l );
				canvas.Children.Add( el1 );
				canvas.Children.Add( el2 );
			}
		}

		/// <summary>
		/// Set the y positions of the lines on the interval, by incrementing the phase
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void myDispatcherTimer_Tick( object sender, EventArgs e ) {
			for ( int i = 0; i < lines.Count; i++ ) {
				Line l = lines[i];
				l.Y1 = ( ActualHeight / 2 ) + calcY1( l.X1, phase );
				double circleY = l.Y1;
				//if (circleY < canvas.ActualHeight / 2) {
				//    circleY -= 10;
				//}
				circles1[i].SetValue( Canvas.TopProperty, circleY );

				l.Y2 = ( ActualHeight / 2 ) + calcY2( l.X1, phase );
				circleY = l.Y2;
				//if (circleY < canvas.ActualHeight / 2) {
				//    circleY -= 10;
				//}
				circles2[i].SetValue( Canvas.TopProperty, circleY );
			}
			phase = ( phase + phaseChange ) % 360;
		}

		/// <summary>
		/// calculate y position of the first strand
		/// </summary>
		/// <param name="x"></param>
		/// <param name="phase"></param>
		/// <returns></returns>
		double calcY1( double x, double phase ) {
			return amplitude * Math.Sin( ( phase + x ) * 2 * Math.PI / 360 );
		}

		/// <summary>
		/// calculate y position of the second strand- 180' out of phase to the first
		/// </summary>
		/// <param name="x"></param>
		/// <param name="phase"></param>
		/// <returns></returns>
		double calcY2( double x, double phase ) {
			return amplitude * Math.Sin( ( phase + 180 + x ) * 2 * Math.PI / 360 );
		}


	}
}
